﻿#region License
/*  YanYeovil
    Copyright (c) 2014, Alexey A. Popov
    All rights reserved.

    Redistribution and use in source and binary forms, with or without modification, are
    permitted provided that the following conditions are met:

    - Redistributions of source code must retain the above copyright notice, this list
      of conditions and the following disclaimer.

    - Redistributions in binary form must reproduce the above copyright notice, this list
      of conditions and the following disclaimer in the documentation and/or other materials
      provided with the distribution.

    - Neither the propertyName of the Alexey A. Popov nor the names of its contributors may be used to
      endorse or promote products derived from this software without specific prior written
      permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS *AS IS* AND ANY EXPRESS
    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
    IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

using System;

namespace Pvax.Data
{
    /// <summary>
    /// The concrete implementation of the getter/setter closure.
    /// </summary>
    /// <typeparam name="TClass">The class the closure is bound to.</typeparam>
    /// <typeparam name="TProperty">The property type.</typeparam>
    sealed class Closure<TClass, TProperty>: Closure
    {
        Func<TClass, TProperty> getter;

        Action<TClass, TProperty> setter;

        /// <summary>
        /// Creates and initializes a new instance of the <see cref="Closure{TClass,TProperty}"/>.
        /// </summary>
        /// <param name="untypedGetter">The getter delegate.</param>
        /// <param name="untypedSetter">The setter delegate.</param>
        internal Closure(Delegate untypedGetter, Delegate untypedSetter)
        {
            if(null == untypedGetter)
                throw new ArgumentNullException("untypedGetter");
            if(null == untypedSetter)
                throw new ArgumentNullException("untypedSetter");

            this.getter = (Func<TClass, TProperty>)untypedGetter;
            this.setter = (Action<TClass, TProperty>)untypedSetter;
        }

        /// <summary>
        /// Returns a value of a property of the <paramref name="instance"/>.
        /// </summary>
        /// <param name="instance">The instance of the object to get a value of the property.</param>
        /// <returns>The value of the property, probably boxed for value types.</returns>
        /// <remarks>
        /// <para>
        /// This is the actual implementation.
        /// </para>
        /// <para>
        /// This method casts the <paramref name="instance"/> to <typeparamref name="TClass"/> and
        /// calls the typed getter delegate stored in this object. The result of the call gets casted
        /// to <see cref="object"/> and returned.
        /// </para>
        /// </remarks>
        internal override object Get(object instance)
        {
            return getter((TClass)instance);
        }

        /// <summary>
        /// Sets the <paramref name="value"/> of the property of the <paramref name="instance"/>.
        /// </summary>
        /// <param name="instance">The instance to set the <paramref name="value"/> for.</param>
        /// <param name="value">The actual value, probaly boxed for value types.</param>
        /// <remarks>
        /// <para>
        /// This is the actual implementation.
        /// </para>
        /// <para>
        /// This method casts the <paramref name="instance"/> to <typeparamref name="TClass"/> and
        /// <paramref name="value"/> to <typeparamref name="TProperty"/> and calls typed setter
        /// delegate stored in this object.
        /// </para>
        /// <para>
        /// Note that if the <paramref name="value"/> implements the <see cref="IConvertible"/>
        /// interface, additional type conversion is performed. The reason is that some DB engines
        /// do not fully support CTS and return only "compatible" type instead of exact type. An
        /// example is SQLite embedded engine that uses <c>INTEGER</c> instead of <c>BOOLEAN</c>.
        /// </para>
        /// </remarks>
        internal override void Set(object instance, object value)
        {
            if(value is IConvertible)
            {
                setter((TClass)instance, (TProperty)Convert.ChangeType(value, typeof(TProperty)));
            }
            else
            {
                setter((TClass)instance, (TProperty)value);
            }
        }
    }
}
